BLOG

Record, summarize, and improve.

debug tools

nm

查看 目标文件(.o、.a、.so、可执行文件)中的符号表,也就是列出程序中有哪些函数、变量、符号,以及它们在文件中的地址和类型。

nm [option(s)] [file(s)]
选项:

  • -C 还原C++函数名(demangle),否则C++函数显示为编译后的符号名(mangled)
  • -D 打印动态符号
  • -n 按地址排序符号
  • -u 只显示未定义(undefined)的符号(外部依赖)
  • --defined-only 只列出已定义的符号
  • -l 使用对象文件中的调试信息打印出所在源文件及行号;
字母 含义
T 已定义的代码(text段)
U 未定义符号(external依赖)
D 已定义的数据(data段)
B BSS段(未初始化数据)
R 只读数据(readonly data)
W 弱符号(weak symbol)
t/d/b 表示局部符号(local,非大写)

注意:
-C 总是适用于c++编译出来的对象文件。还记得c++中有重载么?为了区分重载函数,c++编译器会将函数返回值/参数等信息附加到函数名称中去形成一个mangle过的符号,那用这个选项列出符号的时候,做一个逆操作,输出那些原始的、我们可理解的符号名称。

使用 -l 时,必须保证你的对象文件中带有符号调式信息,这一般要求你在编译的时候指定一个 -g 选项。
使用nm前,最好先用 file 查看对象文件所属处理器架构,然后再用相应交叉版本的nm工具。

ldd

查看一个可执行文件在运行时依赖哪些共享库(动态链接库 .so),以及这些库从哪里加载。

ldd显示可执行模块的dependency的工作原理,其实质是通过ld-linux.so(elf动态库的装载器)来实现的,ld-linux.so模块会先于executable模块程序工作,并获得控制权,因此当上述的那些环境变量被设置时,ld-linux.so选择了显示可执行模块的dependency。

/lib/ld-linux.so.2 --list program(这相当于ldd program)

工具 功能
ldd 查看运行时依赖的共享库路径
readelf -d 查看ELF文件中 .dynamic 段(静态声明)
objdump -p 也可以查看动态依赖信息

objdump

objdump命令是Linux下的反汇编目标文件或者可执行文件的命令
选项

-a
显示档案库的成员信息,类似ls -l将lib*.a的信息列出

-b bfdname
指定目标码格式。这不是必须的,objdump能自动识别许多格式

-g
显示调试信息。企图解析保存在文件中的调试信息并以C语言的语法显示出来

-C 	将底层的符号名解码成用户级名字,除了去掉所开头的下划线之外,还使得C++函数名以可理解的方式显示出来。

-e
类似-g选项,但是生成的信息是和ctags工具相兼容的格式。

-d
从objfile中反汇编那些特定指令机器码的section。

-D-d 类似,但反汇编所有section.

-EB
-EL 	指定目标文件的小端。这个项将影响反汇编出来的指令。在反汇编的文件没描述小端信息的时候用。例如S-records.

-f
--file-headers
显示objfile中每个文件的整体头部摘要信息。

-h
显示目标文件各个section的头部摘要信息

-j name
仅仅显示指定名称为name的section的信息

-r 	显示文件的重定位入口。如果和-d或者-D一起使用,重定位部分以反汇编后的格式显示出来。

-R
显示文件的动态重定位入口,仅仅对于动态目标文件意义,比如某些共享库。

-s
显示指定section的完整内容。默认所有的非空section都会被显示。

-S 	尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,效果比较明显。隐含了-d参数。

-t
显示文件的符号表入口。类似于nm -s提供的信息

-T 	显示文件的动态符号表入口,仅仅对动态目标文件意义,比如某些共享库。它显示的信息类似于 nm -D|--dynamic 显示的信息

-x
显示所可用的头信息,包括符号表、重定位入口。-x 等价于-a -f -h -r -t 同时指定。

-z
一般反汇编输出将省略大块的零,该选项使得这些零块也被反汇编。
  • View all data/code in every section of an ELF file:
    objdump -D <elf_object>
  • View only program code in an ELF file:
    objdump -d <elf_object>
  • View all symbols:
    objdump -tT <elf_object>

objcopy

objcopy -O bfdname infile outfile
objcopy用于将object的部分获全部内容拷贝到另一个object,从而可以实现格式的变换。
raw_binary文件是没有任何结构的,其内容就是赤裸裸的代码与数据的组合体。
将xxx.o转换成s-record文件中,通常涉及到text段
选项

-S
去掉源文件的符号信息和relocation信息

-g
去除掉调试符号信息和相关的段

-R sectionname
去除掉由sectionname指定的section

-O bfdname
输出文件的bfdname

-K symbolname
保留由symbolname指定的符号信息

-x
处理符号

-N symbolname
去除掉由symbolname指定的符号信息

ld

-T File     --script FILE指定一个链接器脚本文件,指示编译器按照脚本进行链接;
-Tbss ADDRESS    bss段的链接地址;
-Ttext ADDRESS   代码段链接地址;
-Tdata ADDRESS  数据段链接地址;
-r --relocateable 产生可重定向的输出,比如,产生一个输出文件它可再次作为‘ld'的输入,这经常被叫做“部分链接”,当我们需要将几个小的.o文件链接成为一个.o文件的时候,需要使用此选项。
-e  指定程序的入口标号;
-l   LibName 指定要链接的库;
-L  Directory 增加库文件的搜索路径;
-o  FILE 设置输出文件名;
-O  输出文件最优;
-a  指定arch体系;
ENTRY(_start)           /* 设置程序入口符号 */

MEMORY {                /* 可选:用于嵌入式设置内存区域 */
  RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 64K
}

SECTIONS {              /* 核心段定义 */
  . = 0x80000000;       /* 指定链接起始地址 */

  .text : {
    *(.init) *(.text) *(.rodata) *(.text.*)
  }

  .data : {
    *(.data) *(.data.*)
  }

  .bss : {
    *(.bss) *(COMMON)
  }
}
_start 通常为程序入口
_edata 数据段结束地址
_etext 代码段结束地址
_end 所有段结束地址
. 当前链接地址指针

ar

创建或更新 .a 静态库(最常用)
ar rcs libfoo.a foo.o bar.o

说明:

  • r:替换或新增成员
  • c:如果库不存在则创建
  • s:更新符号表(建议用于所有静态库)
查看库中有哪些对象文件
ar t libfoo.a

v 显示详细:

ar tv libfoo.a

解压库中的某个或所有 .o 文件
ar x libfoo.a           # 解压所有
ar x libfoo.a bar.o     # 解压指定成员

删除库中的某个成员
ar d libfoo.a old.o

追加多个目标文件到已有 .a 文件中
ar rcs libfoo.a new1.o new2.o

注意事项

  • .a 文件是纯二进制归档,不包含依赖信息,使用时需显式链接,例如:
    gcc main.o -L. -lfoo -o main
  • 编译时建议使用 ranlib 生成符号索引(但 ar rcs 已包含效果):
    ranlib libfoo.a

示例:制作一个静态库

  1. 编译源文件为目标文件:
    gcc -c foo.c bar.c
  2. 创建静态库:
    ar rcs libfoo.a foo.o bar.o
  3. 链接使用:
    gcc main.c -L. -lfoo -o main

readelf

查看 ELF 文件类型和基本信息
readelf -h ./a.out

输出内容包括:

  • 类型(可执行文件/共享库/可重定位)
  • 架构(x86_64, riscv, arm 等)
  • 入口地址
  • 节区数、程序段数

查看程序加载段信息(运行时映射)
readelf -l ./a.out

显示 .text, .data, .bss 等段的加载地址、权限(R/W/X)等信息。


查看节区信息(源代码编译后的分区)
readelf -S ./a.out

列出所有节(如 .text, .data, .rodata, .symtab),帮助理解数据和代码如何组织。


查看符号表(类似 nm
readelf -s ./a.out

或:

readelf --dyn-syms ./libfoo.so

可以查看所有函数、变量的名字、地址、类型、作用域等。


查看动态链接库依赖(类似 ldd
readelf -d ./a.out

会显示 .dynamic 段内容,例如:

  • NEEDED 的库
  • SONAME
  • INIT_ARRAY
  • HASH 表等信息

查看重定位表
readelf -r ./a.out

用于分析 .rel.rela 节(静态链接或动态链接重定位信息)


查看指定节的内容(如字符串表、代码段等)
readelf -x .rodata ./a.out

你可以看到原始的十六进制表示,比如字符串常量、初始化数据等。

addr2line -将地址(十六进制)转换为代码行号
ar -静态库(或存档)工具
as -汇编程序
c++ -C++前端
c++filt -将错误的名称转换为具有原型的功能
cpp -仅预处理器
elfedit -elf标头操作
g++ -具有gnu扩展的C++
gcc -标准二进制文件(给定的选项可以执行与包装程序相同的操作)
gcc-5.4.1 -具有多个GCC安装的系统的全名
gcc-ar -如果有多个“ ar”,则重命名
gcc-nm -如果有多个“ nm”,则重命名
gcc-ranlib -如果有多个“ ranlib”,则重命名
gcov -代码覆盖率
gcov-tool -代码覆盖率
gdb -调试器
gdb-py -更小的调试器
gprof -调用图/分析器
ld -链接器(很可能是金)
ld.bfd -具有更多功能的旧式链接器;对于大型C++项目,速度要慢得多
nm -以二进制形式显示“名称”
objcopy -操作二进制文件(节)
objdump -关于二进制文件的信息
ranlib -生成库索引
readelf -有关ELF二进制文件的信息
size -程序段大小
strings -将所有字符串转储为二进制文件
strip -从二进制文件中删除调试信息

名称“gcc-ar”和“ ar”在物理上是同一件事。但是,路径(Solaris或其他Unix系统)中可能存在另一个“ ar”,并且可以使用“ gcc-ar”名称来获取特定于gcc的“ ar”。所有“ gcc-XXX”都适用于此用例。

addr2line

单个地址转换为源文件位置
addr2line -e ./a.out 0x4005e3

输出示例:

main.c:42

显示函数名(配合 f
addr2line -e ./a.out -f 0x4005e3

输出示例:

main
main.c:42

还原C++函数名(demangle)
addr2line -e ./my_cpp_app -f -C 0x401234

输出示例:

MyClass::doSomething()
src/myclass.cpp:77

批量地址查询(脚本或多地址)
addr2line -e ./a.out -f -C < crash_addresses.txt

其中 crash_addresses.txt 内容例如:

python-repl
CopyEdit
0x4005e3
0x4006a2
...

使用前提

addr2line 要想准确工作,目标文件必须包含调试符号信息(DWARF)

  • 编译时需要加上 g 选项(不要加 sstrip 剥离符号)